# forward-compat boilerplate
from __future__ import absolute_import
from __future__ import with_statement
__metaclass__ = type

import markupsafe

from fenton import util
from fenton import getmeta


JQUERY_LOCAL = 'fenton/js/jquery-1.7.1.js'
JQUERY_CDN = 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js'


# FIXME belongs elsewhere
def iter_views(context, iter):
    iter = (getmeta(obj).get_view(context, obj)
            for obj in iter)
    return (v for v in iter if v)



ALL_CSS = set()
ALL_JS = set()
ALL_MAKO = set(['default'])

core_css = ['fenton/css/reset.css',
            'fenton/css/fenton-core.css',
            'fenton/css/jquery.notify.css']

core_js = [JQUERY_LOCAL,
           'fenton/js/fenton-core.js',
           'fenton/js/jquery.bgiframe.js',
           'fenton/js/jquery.notify.js']


def get_all_css():
    return core_css + list(ALL_CSS - set(core_css))


def get_all_js():
    return core_js + list(ALL_JS - set(core_js))


def register_css(css):
    if isinstance(css, basestring):
        css = [css]
    if css:
        ALL_CSS.update(css)


def register_js(js):
    if isinstance(js, basestring):
        js = [js]
    if js:
        ALL_JS.update(js)



def register_mako(f):
    if isinstance(f, basestring):
        f = [f]
    if f:
        ALL_MAKO.update(f)


class widget:
    template_file = 'fenton_core'
    class_id = property(lambda x:x.__class__.__name__)
    template_name = property(lambda x:util.decamel(x.class_id))
    js_class = property(lambda x:util.decamel(x.class_id))

    class __metaclass__(type):
        def __init__(self, name, bases, attrs):
            type.__init__(self, name, bases, attrs)
            register_css(self.css)
            register_js(self.js)
            register_mako(self.template_file)

    js = css = None

    __id     = None
    id      = util.lazyattr(lambda x:x.__id or x.screen.next_id())
    app     = property(lambda x:x.screen.app)
    model   = property(lambda x:x.screen.model)
    context = property(lambda x:x.screen.context)
    request = property(lambda x:x.screen.request)
    debug   = property(lambda x:x.screen.app.debug)
    config  = property(lambda x:x.screen.app.config)
    cachable = False
    render = True
    hidden = False
    compile_child = None


    def __init__(self, parent=None, **kw):
        self.children = []
        self.set_parent(parent)
        self.__id = kw.pop('id', None)
        self.__dict__.update(kw)

    def __getstate__(self):
        raise TypeError('Widgets may not be pickled')

    def init(self):
        pass

    def get_url(self, obj):
        v = getmeta(obj).get_view(self.context, obj)
        return v and v.get_url()

    @property
    def css_class(self):
        return ' '.join(util.decamel(x).replace('_', '-')
                        for x in self.css_classes() or [])

    def css_classes(self):
        return [self.class_id]

    __parent = None
    @property
    def parent(self):
        return self.__parent

    def set_parent(self, parent):
        self.__parent = parent
        if isinstance(parent, widget):
            self.screen = parent.screen
            parent.children.append(self)
        else:
            self.screen = parent

    def compile(self):
        def _compile(w, compilers):
            cc = w.compile_child
            w.init()
            compilers = cc and compilers + [cc] or compilers
            for child in w:
                _compile(child, compilers)
            for f in compilers:
                f(w)
        return _compile(self, [])

    def __iter__(self):
        return iter(self.children)

    @property
    def args(self):
        args = self.get_args() or {}
        return dict((k, v) for (k, v) in args.iteritems() if v) or None

    def get_args(self):
        pass

    def get_link(self, obj, label=None):
        v = self.context.get_view(obj)
        if not label:
            label = str(obj)
        view = getmeta(obj).get_view(self.context, obj)
        url = view and view.get_url()
        if url:
            return link(self, url=url, label=label, link_class='ref')
        else:
            return text(self, label)




class screen_widget(widget):
    class_id = property(lambda x:x.screen.__class__.__name__)




class custom_widget(widget):
    name = None
    class_id = property(lambda x:x.name or x.__class__.__name__)




class _collector(widget):
    widgets = util.lazyattr(lambda:[])
    def compile_child(self, child):
        self.widgets.append((child.id, child.js_class, child.args))




class screen_frame(widget):
    title = None
    pack_static = property(lambda x:x.app.config.get('pack_static'))

    def stylesheet_urls(self):
        if self.pack_static:
            url = 'css/pack-%s.css' % self.app.static_version
            return [self.request.static_url(url)]
        else:
            s = '?%d' % util.timer()
            return (self.request.static_url(url)+s for url in get_all_css())

    def javascript_urls(self):
        if self.pack_static:
            url = 'js/pack-%s.js' % self.app.static_version
            return [self.request.static_url(url)]
        else:
            s = '?%d' % util.timer()
            return (self.request.static_url(url)+s for url in get_all_js())

    def get_args(self):
        return {'heartbeat_id': self.screen.bind_heartbeat(),
                'heartbeat_seconds': self.request.heartbeat_seconds,
                'expire_seconds': self.request.context_expire_seconds,
                'update_bind': self.screen.bind_update and self.screen.bind_update()}

    def css_classes(self):
        tag = ''
        series = self.app.release_series
        if series:
            tag = 'release-%s' % series
        return [tag,
                self.model.__class__.__name__,
                self.screen.__class__.__name__,
                self.class_id,
                self.debug and 'debug' or '',
                self.screen.editable and 'editable' or 'readonly']

    @property
    def context_location(self):
        return self.request.context_location(self.context)

    def install_script(self):
        vars = {
            'logger_bind': self.screen.bind_logger(),
            'error_message': self.app.default_error_message,
            'xmlhttp_key': self.request.xmlhttp_key,
            'model_key': self.request.model_key,
            'bind_key': self.request.bind_key,
            'debug': self.debug,
        }
        vars.update(self.screen.vars)
        from fenton.types import json
        return '%s(%s)' % (self.screen.namespace,
                           json.dumps(vars, indent=self.debug and 2 or None))




class header_panel(widget):
    title = None
    max_crumbs = 5
    crumb_prefix = '&raquo;'
    crumb_ellipsis = '&#8230;'

    def _screen_trail(self):
        x = self.screen.get_parent()
        while x:
            yield x
            x = x.get_parent()

    @property
    def screen_trail(self):
        crumbs = list(self._screen_trail())
        if len(crumbs) > self.max_crumbs:
            crumbs = crumbs[:(self.max_crumbs-1)] + [None]
        for v in reversed(crumbs):
            if v is None:
                yield text(self, markupsafe.Markup(self.crumb_ellipsis))
            elif self.screen.modal:
                yield text(self, v.get_title())
            else:
                yield link(self, url=v.get_url(), label=v.get_title(), link_class='crumb')

    @property
    def logo_html(self):
        return self.app.logo_html(self.screen)

    @property
    def logo_image(self):
        return self.request.static_url(self.app.global_logo)

    @property
    def logo_url(self):
        return self.request.absolute_url(self.app.home_url)

    @property
    def logout_url(self):
        return self.request.absolute_url(self.app.logout_url)

    @property
    def help_url(self):
        return self.request.absolute_url(self.app.help_url)

    @property
    def release_label(self):
        return self.app.release_label or ''

    @property
    def release_version(self):
        return self.app.release_version or ''

    @property
    def session_links(self):
        if self.request.user.authenticated:
            yield text(self, self.request.user.name)
            if not self.screen.modal:
                #yield link(self, label='profile', url=self.logout_url)
                #if self.screen.check(security.RESTRICTED):
                #    yield link(self, label='switch', url=self.logout_url)
                yield link(self, label='logout', url=self.logout_url)
        else:
            yield text(self, 'Not logged in')



class action_panel(widget):
    template_name = 'action_panel'


class body_panel(widget):
    template_name = 'bare_panel'


class content_panel(widget):
    template_name = 'content_panel'


class left_actions(action_panel):
    css_class = 'action-panel left-panel'


class top_actions(content_panel):
    css_class = 'action-panel top-panel'


class right_body(body_panel):
    css_class = 'body-panel right-panel'


class sub_body(body_panel):
    css_class = 'body-panel sub-panel'


class sheet_body(content_panel):
    css_class = 'body-panel sheet-panel'




class link(widget):
    link_class=None
    text_class=None


class parent_link(link):
    pass


class text(widget):
    text_class = None
    def __init__(self, parent, text, *args, **kw):
        widget.__init__(self, parent, *args, **kw)
        self.text = text
    def __unicode__(self):
        return unicode(self.text)




class screen_title(widget):
    title = None
    @property
    def render(self):
        return bool(self.title)




class model_list(widget):
    meta = property(lambda x:x.model.submeta)
    list_items = property(lambda x:list(x.screen.model))
    offset = 0
    columns = None
    link_column = False
    fixed_column = None


    @property
    def show_header(self):
        return len(self.columns or []) > 1 and bool(self.list_items)

    def iter_rows(self):
        if not self.columns:
            return []
        return self._iter_rows()

    def _iter_rows(self):
        link_col = self.link_column
        if link_col is None:
            link_col = self.columns[0]
        for i, view in enumerate(iter_views(self.context, self.list_items)):
            url = view.get_url()
            row = []
            for j, k in enumerate(self.columns):
                label = self.meta.format_attribute(view.model, k, self.context)
                if link_col is not None and link_col == k:
                    cell = link(self, label=label, url=url, link_class='ref')
                else:
                    cell = text(self, label, url=None)
                row.append((j, cell))
            yield i, row

    def iter_headers(self):
        for key in self.columns:
            t = self.meta.attributes[key]
            yield t.get_label()




class field_widget(widget):
    key     = property(lambda x:x.field.key)
    name    = property(lambda x:x.field.name)
    type    = property(lambda x:x.field.type)
    hint    = property(lambda x:x.field.hint)
    error   = property(lambda x:x.field.error)
    index   = property(lambda x:x.field.index)
    label   = property(lambda x:x.field.label)
    value   = property(lambda x:x.field.get_value())
    required = property(lambda x:x.field.required)
    editable = property(lambda x:x.field.editable)
    formatted = property(lambda x:x.field.get_formatted())
    simplified = property(lambda x:x.get_simplified())

    change_on_dirty = True
    change_refreshes = False

    @property
    def args(self):
        args = self.get_args() or {}
        return dict(field=self.field.key,
                    change_refreshes=self.change_refreshes,
                    change_on_dirty=self.change_on_dirty,
                    **(self.get_args() or {}))

    def css_classes(self):
        return (self.class_id, self.type.__class__.__name__)

    def get_simplified(self):
        v = self.field.get_simplified()
        return v is not None and v or ''



class string_edit(field_widget):
    keypress_triggers = True
    def get_args(self):
        return {'keypress_triggers_dirty': self.keypress_triggers}




class code_edit(string_edit):
    template_name = 'string_edit'
    def get_args(self):
        return {'case': self.type.case,
                'chars': self.type.chars}




class textbox_edit(string_edit):
    columns = 50
    rows = 5




class date_edit(string_edit):
    js = ['fenton/js/jquery.dateinput.js']
    css = ['fenton/css/jquery.dateinput.css']

    def format_date(self):
        if not self.value:
            return None
        d = self.value.day
        return '%d %s' % (d, self.value.strftime('%b %Y'))




class time_edit(string_edit):
    def format_time(self):
        return self.field.get_formatted()




class minutes_edit(string_edit):
    pass




class local_datetime_view(field_widget):

    @property
    def tz(self):
        return self.type.get_tz(self.model)

    @property
    def formatted_date(self):
        return self.formatted and self.type.format_date(self.value, self.context) or ''

    @property
    def formatted_time(self):
        return self.formatted and self.type.format_time(self.value, self.context) or ''

    @property
    def formatted_tz(self):
        return str(self.type.get_tz(self.model))




class local_datetime_edit(date_edit, local_datetime_view):
    @property
    def simplified(self):
        return local_datetime_view.get_simplified(self)

    @property
    def tzoffset(self):
        return self.tz.minutes

    def get_args(self):
        args = {'tzoffset': self.tzoffset}
        if self.type.follows_from:
            follow_field = self.field.form.fields[self.type.follows_from]
            args['follows_from'] = follow_field.widget.id
        return args




class option_list(field_widget):
    other_key = property(lambda x:x.type.other_key)
    other_label = property(lambda x:x.type.other_label)
    multiple=False

    def _iter_options(self):
        return self.get_type().get_options(self.field)

    def iter_options(self):
        type = self.get_type()
        has_selected = False
        for i, (key, obj) in enumerate(self._iter_options()):
            selected = self.is_selected(obj) and (self.multiple or not has_selected)
            id = '%s_select_option_%d' % (self.field.key, i)
            yield id, key, type.format_value(obj, self.context), selected
            has_selected |= (selected or False)




class simple_select_list(option_list):
    multiple = True
    scrolling = True
    scroll_height = None
    def get_type(self):
        return self.type.of
    def is_selected(self, value):
        return bool(self.value and value in (self.value or []))




class single_option_select(option_list):
    null_option_empty = 'Select...'
    null_option_value = '(none)'
    def get_null_option(self):
        if not self.field.get_value():
            return self.null_option_empty
        #elif not self.field.required:
        else:
            return self.null_option_value

    def _iter_options(self):
        if self.get_null_option():
            yield '', self.get_null_option()
        for x in option_list._iter_options(self):
            yield x

    def get_args(self):
        return {'other_key': self.other_key,
                'null_option_empty': self.null_option_empty,
                'null_option_value': self.null_option_value}

    def get_type(self):
        return self.type

    def is_selected(self, value):
        return self.value is not None and value == self.value




class radio_select(single_option_select):
    orient = 'vertical'
    null_option_value = None
    null_option_empty = None

    def get_null_option(self):
        if not self.field.get_value() and self.field.required:
            return self.null_option_empty
        elif not self.field.required:
            return self.null_option_value



class boolean_edit(radio_select):
    class_id = 'radio_select'
    orient = 'horizontal'
    def is_selected(self, value):
        return self.value is not None and bool(value) == bool(self.value)




class reference_view(field_widget):
    def init(self):
        label = self.type.format_value(self.value, self.context)
        if self.screen.modal or not self.type.link or not self.value:
            self.link = text(self, text=str(label))
        elif self.value:
            view = getmeta(self.value).get_view(self.context, self.value)
            self.link = link(self, url=view.get_url(), label=label, link_class='ref')




class reference_list_view(model_list, field_widget):
    list_items = property(lambda x:x.value)
    meta = property(lambda x:x.type.of.meta)
    columns = property(lambda x:x.type.columns)
    link_column = property(lambda x:x.type.link_column)

    @property
    def show_header(self):
        return self.type.show_header and len(self.columns or []) > 1 and bool(self.list_items)




class child_single_view(model_list, field_widget):
    meta = property(lambda x:x.type.meta)
    columns = property(lambda x:x.type.columns)
    link_column = property(lambda x:x.type.link_column)
    @property
    def list_items(self):
        v = self.field.get_value()
        if v is None:
            return []
        return [v]

    @property
    def show_header(self):
        return self.type.show_header and len(self.columns or []) > 1 and bool(self.list_items)




class child_subedit_widget(field_widget):
    meta = property(lambda x:x.type.meta)
    columns = property(lambda x:x.type.columns)
    link_column = property(lambda x:x.type.link_column)
    addable = True

    @property
    def render(self):
        priv = self.meta.get_priv('view')
        from fenton import security
        return security.check(priv, self.context, self.value)

    def bind_create(self, index=None):
        return self.screen.bind_create_child(self.key, index)

    def bind_edit(self, index=None):
        if index is None:
            value = self.value
        else:
            value = self.value[index]
        return self.screen.bind_edit_child(value, self.key, index)

    def bind_delete(self, index=None):
        if index is None:
            value = self.value
        else:
            value = self.value[index]
        return self.screen.bind_delete_child(value, self.key, index)




class child_single_edit(child_single_view, child_subedit_widget):
    columns = property(lambda x:x.type.columns)
    link_column = property(lambda x:x.type.link_column)
    def init(self):
        self.addable = self.value is None

    def add_bind(self):
        return self.bind_create()

    def iter_rows(self):
        for i, row in child_single_view.iter_rows(self):
            yield i, row, self.bind_edit(), self.bind_delete()




class child_list_view(model_list, field_widget):
    meta = property(lambda x:x.type.of.meta)
    list_items = property(lambda x:x.value)
    columns = property(lambda x:x.type.columns)
    link_column = property(lambda x:x.type.link_column)

    @property
    def show_header(self):
        return self.type.show_header and len(self.columns or []) > 1 and bool(self.list_items)




class child_list_edit(child_list_view, child_subedit_widget):
    def iter_rows(self):
        self.i = 0
        for i, row in child_list_view.iter_rows(self):
            self.i += 1
            yield i, row, self.bind_edit(i), self.bind_delete(i)

    def add_bind(self):
        return self.bind_create(self.i)




class toolbar_button(widget):
    confirm = None
    button_class = property(lambda x:x.type + '-action')
    disabled = False
    dirty_disables = False
    def get_args(self):
        return dict(disabled = self.disabled,
                    confirm = self.confirm,
                    dirty_disables = self.dirty_disables)




class toggle_button(field_widget):
    disabled = False
    def get_args(self):
        return {'label_on': self.label_on,
                'label_off': self.label_off}




def navbuttons_navigate(screen, n):
    screen.model += n
    screen.refresh()




class navbuttons(toolbar_button):
    label_back = u'\N{LEFTWARDS ARROW}'
    label_forward = u'\N{RIGHTWARDS ARROW}'

    @util.lazyattr
    def bind_back(self):
        if self.model.__iadd__(-1, True):
            return self.screen.bind(navbuttons_navigate, self.screen, -1)

    @util.lazyattr
    def bind_forward(self):
        if self.model.__iadd__(1, True):
            return self.screen.bind(navbuttons_navigate, self.screen, 1)




class submit_button(widget):
    button_type = 'submit'
    button_class = property(lambda x:x.type + '-action')




class button_bar(widget):
    @property
    def render(self):
        return bool(self)




class filter_field(widget):
    def init(self):
        self.field.widget.set_parent(self)
    def css_classes(self):
        return self.children[0].css_classes()




class model_form(widget):
    def init(self):
        for key, header, fields in self.groups:
            field_group(self, key=key.replace('_', '-'), header=header, fields=fields)




class field_group(widget):
    def init(self):
        for f in self.fields:
            f.widget.set_parent(self)




class server_error(widget):
    template_file = 'error'



## template-only widgets

class dialog_buttons(widget):
    pass


class textblock(text):
    pass


class link_list(widget):
    pass


class spacer(widget):
    pass


class message(widget):
    pass


class string_view(field_widget):
    pass


class text_view(field_widget):
    pass


class string_mask_view(string_view):
    pass


class string_mask_edit(string_edit):
    pass


class date_view(field_widget):
    pass


class datetime_view(field_widget):
    pass


class datetime_edit(field_widget):
    pass


class simple_list_view(field_widget):
    pass


class simple_list_edit(field_widget):
    pass


class boolean_view(field_widget):
    pass


class date_period_view(field_widget):
    pass


class date_period_edit(field_widget):
    pass


class model_history(widget):
    pass


class message_button(text):
    pass



